In [1]:
import nbimport
from Aircraft_Kinematics import body_b, body_w, frame_i, frame_b, frame_w, bke, t, m, point_cm_w, \
V_T, alpha, beta, sol_rot, sol_euler_rates, sol_vel_wind, P, Q, R, phi, theta, psi, M_bx, M_by, M_bz, \
point_cm_b, M_wx, M_wy, M_wz, J_x, J_y, J_z, J_xz, sol_trans_wind, F_wx, F_wy, F_wz
import sympy
import scipy.optimize
import statespace
import pylab as pl
import control
g, L, C, D, T = sympy.symbols('g, L, C, D, T')
ail, elv, rdr, thr = sympy.symbols('ail, elv, rdr, thr')
%load_ext autoreload
%autoreload 2
%precision 3
Out[1]:
In [2]:
F = m*g*frame_i.z - L(t)*frame_w.z - C(t)*frame_w.y - D(t)*frame_w.x + T(t)*frame_b.x
F
Out[2]:
In [3]:
di_L_i = bke(body_w.linear_momentum(frame_i), frame_i, frame_w, t)
sol_trans = sympy.solve((di_L_i - F).to_matrix(frame_w), [xi(t).diff(t) for xi in [V_T, alpha, beta]])
sol_trans
Out[3]:
In [4]:
M = frame_w.x*M_wx(t) + frame_w.y*M_wy(t) + frame_w.z*M_wz(t)
In [5]:
di_H_i = bke(body_b.angular_momentum(point_cm_b, frame_i), frame_i, frame_b, t)
di_H_i
Out[5]:
In [6]:
sol_rot = sympy.solve((M - di_H_i).to_matrix(frame_b), [xi(t).diff(t) for xi in [P, Q, R]])
sol_rot
Out[6]:
In [7]:
eoms = {}
eoms.update(sol_euler_rates)
eoms.update(sol_trans)
eoms.update(sol_vel_wind)
eoms.update(sol_rot)
x_vect = sympy.Matrix([Q, theta, V_T, alpha, P, R, phi, beta])
u_vect = sympy.Matrix([elv, thr, ail, rdr])
sub_no_t = {xi(t): xi for xi in x_vect}
f_vect = sympy.Matrix([eoms[xi(t).diff(t)].subs(sub_no_t) for xi in x_vect])
f_vect
Out[7]:
In [8]:
C_L_0, C_L_alpha, k_CD_CL, C_D_0, CL_CD_min, rho, S = \
sympy.symbols('C_L0 C_L_alpha k_CD_CL C_D_0 CL_CD_min, rho, S')
C_L = C_L_0 + C_L_alpha*alpha
C_D = C_D_0 + k_CD_CL*(C_L - CL_CD_min)**2
C_C = beta # todo
C_T = 10
C_l = -P -beta + ail
C_m = -Q -alpha + elv
C_n = -R -beta + rdr
q = rho*V_T**2/2
sub_aero_model = {
L(t): C_L*q*S,
D(t): C_D*q*S,
C(t): C_C*q*S,
T(t): C_T*thr,
M_wx(t): C_l*q*S,# todo
M_wy(t): C_m*q*S,# todo
M_wz(t): C_n*q*S,# todo
}
sub_aero_model
Out[8]:
In [9]:
f_aero_vect = f_vect.subs(sub_aero_model).applyfunc(lambda e:e.simplify())
f_aero_vect
Out[9]:
In [10]:
param = f_aero_vect.atoms(sympy.Symbol).difference(x_vect)
param
Out[10]:
In [11]:
#x_id = sympy.Matrix(list(x_vect) + list(param))
#f_id_vect = sympy.Matrix(list(f_aero_vect) + list(sympy.zeros(len(param))))
#A_id = f_id_vect.jacobian(x_id)
#sub_x0 = {P:0, Q:0, R:0, phi:0, theta:0, alpha:0, beta:0}
#A_id.subs(sub_x0)
In [12]:
sub_const = {
J_x:1, J_y:1, J_z:1, J_xz:0, m:1, rho:1.225, S:1, g:10,
C_L_alpha:3, C_L_0: 0.1, C_D_0: 0.01, k_CD_CL: 0.01, CL_CD_min:0,
}
In [13]:
x_vect.T, u_vect.T
Out[13]:
In [14]:
def trim_level(V_T, gamma):
f1 = lambda theta, elv, thr: \
pl.sum(ss.f_eval(0, [0, theta, V_T, gamma-theta, 0, 0, 0, 0], [elv, thr, 0, 0])**2)
res = scipy.optimize.minimize(lambda x:f1(theta=x[0], elv=x[1], thr=x[2]), [0,0,0])
theta_trim = res.x[0]
elv_trim = res.x[1]
thr_trim = res.x[2]
return [0, theta_trim, V_T, gamma - theta_trim, 0, 0, 0, 0], [elv_trim, thr_trim, 0, 0], res
In [15]:
ss = statespace.StateSpace(x_vect, u_vect, f_aero_vect.subs(sub_const), x_vect)
x0, u0, res = trim_level(20, 0)
data = ss.simulate(x0, u0,dt=0.01,tf=60)
pl.plot(data.t, data.y);
pl.grid()
x0, u0
Out[15]:
In [16]:
ss_lin = ss.linearize(x0, u0)
control.bode(ss_lin.to_control()[0,0], omega=pl.logspace(-2,2));
In [17]:
control.rlocus(ss_lin.to_control()[0,0], klist=pl.linspace(0,1,1000));
pl.grid()
In [18]:
C_L_eval = sympy.lambdify(alpha, C_L.subs(sub_const))
C_D_eval = sympy.lambdify(alpha, C_D.subs(sub_const))
alpha_val = pl.linspace(-pl.deg2rad(15),pl.deg2rad(15))
pl.figure()
pl.plot(alpha_val, C_L_eval(alpha_val))
pl.xlabel(r'$\alpha$')
pl.ylabel(r'$C_L$')
pl.grid()
pl.figure()
pl.plot(C_L_eval(alpha_val), C_D_eval(alpha_val))
pl.xlabel(r'$C_L$')
pl.ylabel(r'$C_D$')
pl.grid()
In [19]:
K, S, E = control.lqr(ss_lin.A, ss_lin.B, pl.eye(8), pl.eye(4))
K
Out[19]:
In [20]:
class Autopilot(object):
"""
This class is a simple autopilot with an estimator and controller.
"""
def __init__(self, x0, u0, f_eval, g_eval):
self.time_contr = 0
self.time_est = 0
self.ode = scipy.integrate.ode(f_eval)
def predict(self, t):
self.ode.integrate(t)
In [21]:
def f_contr(t, y):
x = y
return u0-K.dot(x-x0)
In [22]:
x1 = x0 + pl.rand(8)
data_lin = ss_lin.simulate(x1, u0, contr_eval=f_contr, dt=0.02)
data = ss.simulate(x1, u0, contr_eval=f_contr, dt=0.02)
h0 = pl.plot(data.t, data.x, 'b');
h1 = pl.plot(data_lin.t, data_lin.x, 'r');
pl.legend([h0[0], h1[0]], ['non-lin', 'lin'], loc='best')
pl.grid()
In [23]:
pl.plot(data.t, data_lin.x - data.x);
pl.title('linearization error')
pl.grid()
In [24]:
pl.plot(data.t, data.u);
pl.grid()
In [25]:
K_clean = pl.where(abs(K) < 1e-1, pl.zeros(K.shape), K)
K_clean
Out[25]:
Not we characterize how the controller is actually implemented.
In [26]:
P_r, Q_r, R_r, k_P, k_Q, k_R, k_theta, k_phi, k_alpha, k_dh, k_ah, dh_r, V_T_r, k_V_thr, k_rdr_beta, k_ail_beta, k_alpha_thr, alpha_r = \
sympy.symbols('P_r, Q_r, R_r, k_P, k_Q, k_R, k_theta, k_phi, k_alpha, k_dh, k_ah, dh_r, V_T_r, k_V_thr, k_rdr_beta, k_ail_beta, k_alpha_thr, alpha_r')
In [27]:
phi_r = k_P *(P_r - P)
ail_r = k_phi*(phi_r - phi) + k_ail_beta*beta
theta_r = k_Q *(Q_r - Q)
elv_r = k_theta*(theta_r - theta) - k_alpha*(alpha_r - alpha)
rdr_r = k_R *(R_r - R) + k_rdr_beta*beta
thr_r = -k_dh*(dh_r - V_T*sympy.sin(theta - alpha)) + k_V_thr*(V_T_r - V_T) - k_alpha_thr*(alpha_r - alpha)
u_r = sympy.Matrix([elv_r, thr_r, ail_r, rdr_r])
u_r
Out[27]:
In [28]:
u_vect.T, x_vect.T
Out[28]:
In [29]:
sub_trim = {x_vect[i]: x0[i] for i in range(len(x_vect))}
sub_trim.update({u_vect[i]: u0[i] for i in range(len(u_vect))})
sub_trim
Out[29]:
In [30]:
u_r = sympy.Matrix([elv_r, thr_r, ail_r, rdr_r])
K_syms = -u_r.jacobian(x_vect).subs(sub_trim)
K_syms
Out[30]:
We want to find the gains in the designed controller that give the LQR gains.
In [31]:
K_diff = list(pl.where(abs(K) < 1e-1, pl.zeros(K.shape), K) - K_syms)
K_eqs = sympy.Matrix([ K_diff[i] for i in pl.nonzero(K_diff)[0]])
K_eqs
Out[31]:
In [32]:
len(K_eqs), len(K_eqs.atoms(sympy.Symbol))
Out[32]:
Here, we solve for the gains in the designed controller to match the LQR gains.
In [33]:
sol_K = sympy.solve(K_eqs)[0]
sol_K
Out[33]:
We see that the solution matches the LQR solution at the trim conditions.
In [34]:
K_d = pl.array(K_syms.subs(sol_K)).astype(float)
K_d
Out[34]:
In [35]:
K_clean
Out[35]:
In [36]:
pl.eig(ss_lin.A - ss_lin.B.dot(K_d))[0]
Out[36]:
In [37]:
pl.eig(ss_lin.A - ss_lin.B.dot(K_clean))[0]
Out[37]:
In [38]:
pl.eig(ss_lin.A - ss_lin.B.dot(K))[0]
Out[38]: